Skip to main content

Quotes Provider

Provider supplies quotes of the selected trading instrument, to display this information in the trading widget.

/**
* Interface for receiving quotes data
*
* When loading dxCharts library chart, quotes data is taken from this interface
*
* When connecting dxCharts library, developer can implement this interface or use the default implementation [com.devexperts.dxcharts.provider.quotes.DxFeedQuotesProvider] and pass it to the library using [DxChartsDataProviders] data class
*
* Use [dataFlow] to get quotes data
*
* Use [changeSymbol] to change quotes' instrument symbol
*/
interface DxChartsQuotesProvider {
/**
* Flow of receiving quotes data
*
* Quotes data is represented by [Quote]
*/
val dataFlow: StateFlow<Quote?>
/**
* Changes quotes' instrument symbol
*
* @param symbol instrument symbol
*/
fun changeSymbol(symbol: String)
}

With changeSymbol method, the symbol of the instrument can be changed.

Data is sent by updating the state of the dataFlow variable. It is represented as com.devexperts.dxcharts.provider.domain.Quote object.

/**
* Quote - data with instruments' statistics from exchange
*
* Stores ask, bid prices and sizes
*/
data class Quote(
val bidPrice: Double,
val askPrice: Double,
)

Trading Widget with the quotes passed into it:

trading

Here is the default implementation of DxChartsQuotesProvider:

/**
* Implementation of [DxChartsQuotesProvider] that uses the dxFeed API.
*
* The library "com.devexperts.qd:qds" is used to connect to the dxFeed API.
*
* The process of obtaining quotes is performed in a separate thread [ExecutorService] using [endpoint] from method [connect]:
* - [endpoint] connects to the dxFeed API at the [endpointAddress] in the [connect] method.
* - A subscription [sub] is created to receive quotes.
* - An [eventListener] is added to the subscription [sub] to handle the received quotes.
* - When a quote is received in [eventListener], it is transformed into [com.devexperts.dxcharts.provider.domain.Quote]
* using the extension function [toDataObject] and sent to [dataFlow].
* - Errors encountered during operations are reported via [errorFlow], using instances of [QuoteProviderError].
*
* When the symbol changes [changeSymbol], the subscription [sub] is disconnected from the old symbol and connected to the new one.
*
* When disconnecting [disconnect] from the dxFeed API, the subscription [sub] is disconnected from the symbol and cleared.
*
* @property [endpoint] Object for working with the dxFeed API.
* @property [executorService] Object for starting a thread to retrieve quotes.
* @property [sub] Subscription object of [endpoint] with the instrument symbol [currentSymbol].
* @property [currentSymbol] Current symbol of the instrument.
* @property [_dataFlow] Internal [MutableStateFlow] for sending quotes.
* @property [dataFlow] [StateFlow] for sending quotes.
* @property [_errorFlow] Internal [MutableStateFlow] for sending errors.
* @property [errorFlow] [StateFlow] for sending errors.
* @property [eventListener] Handler for quotes received from [sub].
*/
class DxFeedQuotesProvider(private val endpointAddress: String = "") : DxChartsQuotesProvider,
DxChartsErrorProvider<QuoteProviderError> {
private val endpoint = DXEndpoint.getInstance()
private val sub: DXFeedSubscription<Quote> = endpoint.feed.createSubscription(Quote::class.java)
private val _dataFlow = MutableStateFlow<com.devexperts.dxcharts.provider.domain.Quote?>(null)
override val dataFlow: StateFlow<com.devexperts.dxcharts.provider.domain.Quote?> get() = _dataFlow
private val _errorFlow = MutableStateFlow<QuoteProviderError?>(null)
override val errorFlow: StateFlow<QuoteProviderError?> get() = _errorFlow
private val eventListener = DXFeedEventListener<Quote> {
try {
val quote = it.last()
_dataFlow.tryEmit(quote?.toDataObject())
} catch (e: Exception) {
_errorFlow.tryEmit(QuoteProviderError.DataProcessingError("Error during processing quotes: $e.message", e))
}
}
private var currentSymbol: String? = null
private var executorService: ExecutorService? = null
/**
* Connects to the dxFeed API [endpoint] at the [endpointAddress],
* subscribes [sub] to receive quotes, connects [eventListener],
* and starts the process of obtaining quotes in a separate thread [executorService].
* Handles connection errors and updates [_errorFlow].
*/
fun connect() {
try {
_errorFlow.tryEmit(null)
endpoint.connect(endpointAddress)
sub.addEventListener(eventListener)
if (executorService == null || executorService?.isShutdown == true) {
executorService = Executors.newFixedThreadPool(1)
executorService?.execute {
try {
endpoint.awaitNotConnected()
} catch (e: Exception) {
_errorFlow.tryEmit(QuoteProviderError.NetworkError("Connection to quote provider was interrupted: $e.message}", e))
}
}
} else {
Log.w(TAG, "ExecutorService is already running.")
}
} catch (e: Exception) {
_errorFlow.tryEmit(
QuoteProviderError.ConnectionError("Unknown error during connection to quote provider: $e.message", e)
)
}
}
/**
* Removes [eventListener] from [sub], clears [sub] from [endpoint],
* disconnects [endpoint] from the dxFeed API, and shuts down [executorService].
* Handles disconnection errors and updates [_errorFlow].
*/
fun disconnect() {
try {
sub.removeEventListener(eventListener)
endpoint.feed.detachSubscriptionAndClear(sub)
endpoint.disconnectAndClear()
executorService?.shutdown()
executorService = null
} catch (e: Exception) {
_errorFlow.tryEmit(
QuoteProviderError.ConnectionError("Unknown error during disconnection of quote provider $e.message", e)
)
}
}
/**
* Extension function for transforming [Quote] into [com.devexperts.dxcharts.provider.domain.Quote].
*/
private fun Quote.toDataObject() = com.devexperts.dxcharts.provider.domain.Quote(
bidPrice = bidPrice,
askPrice = askPrice,
)
/**
* Method to change the instrument symbol [currentSymbol] in the subscription [sub].
*
* When the symbol changes [currentSymbol], the subscription [sub] is disconnected from the old symbol,
* connected to the new one, and [dataFlow] is cleared.
*/
override fun changeSymbol(symbol: String) {
if (currentSymbol == symbol) {
return
}
currentSymbol?.let {
try {
sub.removeSymbols(currentSymbol)
_dataFlow.tryEmit(null)
} catch (e: Exception) {
_errorFlow.tryEmit(
QuoteProviderError.DataProcessingError(
"Error during processing quotes: $e.message",
e
)
)
}
}
currentSymbol = symbol
try {
sub.addSymbols(symbol)
} catch (e: Exception) {
_errorFlow.tryEmit(
QuoteProviderError.DataProcessingError("Error adding symbol: $e.message", e)
)
}
}
companion object {
private const val TAG = "DxFeedQuoteProvider"
}
}
/**
- Sealed class representing different types of errors that can occur in the provider.
*/
sealed class QuoteProviderError(override val message: String, override val error: Throwable?) : ProviderError {
data class NetworkError(
override val message: String,
override val error: Throwable? = null
) : QuoteProviderError(message, error)
data class ConnectionError(
override val message: String,
override val error: Throwable? = null
) : QuoteProviderError(message, error)
data class DataProcessingError(
override val message: String,
override val error: Throwable? = null
) : QuoteProviderError(message, error)
}